#ifndef __EVENTS_ZYS_QUEUE_HPP__
#define __EVENTS_ZYS_QUEUE_HPP__

#include "connection.hpp"
#include "event.hpp"
#include <array>
#include <type_traits>
#include <variant>
namespace zevent {

///
/// \brief Event queue template class for storing and processing events
///
/// \tparam kSize Maximum size of the queue
/// \tparam Ts Event service types stored in the queue
///
template <std::size_t kSize, typename... Ts> class Queue {
public:
  /// \brief Ensure at least one event type is specified
  static_assert(sizeof...(Ts) != 0);

  ///
  /// \brief Constructor that registers this queue with all event services
  ///
  constexpr Queue() { (Ts::SetQueue(*this), ...); }

  ///
  /// \brief Type alias for the event type stored in the queue
  /// If only one event type, use that type directly; otherwise use variant
  ///
  using EventType = std::conditional_t<
      (sizeof...(Ts) == 1),
      event::internal::Event<std::tuple_element_t<0, std::tuple<Ts...>>>,
      std::variant<event::internal::Event<Ts>...>>;

  ///
  /// \brief Push an event into the queue
  ///
  /// \param event Event to be pushed into the queue
  ///
  static auto Push(auto event) {
    if (m_size == kSize) {
      return;
    }
    if constexpr (sizeof...(Ts) == 1) {
      m_pool[(m_head + m_size++) % kSize] = event;
    } else {
      new (&m_pool[(m_head + m_size++) % kSize])
          EventType{std::forward<decltype(event)>(event)};
    }
  }

  ///
  /// \brief Pop and process one event from the queue
  ///
  static auto Pop() -> void {
    if (IsEmpty()) {
      return;
    }

    if constexpr (sizeof...(Ts) == 1) {
      m_pool[m_head].Call();
    } else {
      std::visit(
          [](auto &event) {
            auto conn = std::get<ConnectionPtr<
                typename std::decay_t<decltype(event)>::ServiceType>>(
                m_connections);
            if (conn != nullptr) {
              conn->DoCall(event.GetArgs());
            }
          },
          m_pool[m_head]);
    }

    m_head = (m_head + 1) % kSize;
    --m_size;
  }

  ///
  /// \brief Process all events in the queue
  ///
  static auto ProcessEvents() {
    while (not IsEmpty()) {
      Pop();
    }
  }

  template <typename TService, typename TFn> static auto Connect(TFn fn) {
    static callback::internal::Connection<TService, fn> n;
    std::get<ConnectionPtr<TService>>(m_connections) = &n;
  }

  ///
  /// \brief Check if the queue is empty
  ///
  /// \return true if queue is empty, false otherwise
  ///
  static auto IsEmpty() { return m_size == 0; }

  /// \brief Destructor (TODO: implement proper cleanup)
  ~Queue() = default;

private:
  template <typename TService>
  using ConnectionPtr = callback::internal::ConnectionBase<TService> *;
  using TupleOfConnections = std::tuple<ConnectionPtr<Ts>...>;
  /// \brief Static array to store events
  inline static std::array<EventType, kSize> m_pool;

  /// \brief Current position/index in the queue
  inline static int m_head{0};
  inline static int m_size{0};
  inline static TupleOfConnections m_connections;
};

///
/// \brief Trait to check if a type is a Queue
///
template <typename T> struct is_queue : public std::false_type {};

///
/// \brief Specialization of is_queue trait for Queue types
///
template <std::size_t kSize, typename... Ts>
struct is_queue<Queue<kSize, Ts...>> : public std::true_type {};

///
/// \brief Variable template for checking if a type is a Queue
///
template <typename T> constexpr bool is_queue_v = is_queue<T>::value;

} // namespace zevent

#endif